package com.sangharsh.webapp.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import com.sangharsh.core.algo.DirectedEdge;
import com.sangharsh.core.algo.EdgeColorComparator;
import com.sangharsh.core.algo.Journey;
import com.sangharsh.core.algo.Node;
import com.sangharsh.core.algo.Route;
import com.sangharsh.core.algo.StopNode;
import com.sangharsh.core.algo.Tree;
import com.sangharsh.core.algo.Vertex;
import com.sangharsh.core.algo.base.Color;
import com.sangharsh.core.algo.base.Mode;
import com.sangharsh.core.algo.location.LocationUtil;

public class RouteSearchServiceImpl implements RouteSearchService{
	

	private static final Log LOG = LogFactory.getLog(RouteSearchServiceImpl.class);
	
	private Map<Vertex, Set<DirectedEdge>> graph;

	public RouteSearchServiceImpl(StubService stubService){
		graph = new HashMap<Vertex, Set<DirectedEdge>>();
		setUpGraph(stubService);
	}

	private void setUpGraph(StubService stubService) {
		for (DirectedEdge edge : stubService.getAllEdges()) {
			LOG.debug("INITIALIZING: "+edge);
			addEdge(edge);
			addEdge(new DirectedEdge(edge.getDestination(), edge.getSource(), edge.getMode(), edge.getColor()));
		}
	}

	private void addEdge(DirectedEdge edge) {
		Set<DirectedEdge> graphEdges = this.graph.get(edge.getSource());
		if(graphEdges == null){
			graphEdges = new HashSet<DirectedEdge>();
		}
		graphEdges.add(edge);
		graph.put(edge.getSource(), graphEdges);
	}

	private Route<Journey> filter(List<DirectedEdge> path, int stopCount) {
		LinkedList<Journey> journeys = new LinkedList<Journey>();
		int changes = 0;	
		Collections.reverse(path);
		int i=1;
		while(i<path.size()) {
			DirectedEdge edge = path.get(i);
			if(!journeys.isEmpty() && journeys.getLast().getColorCode().equals(edge.getColor())){
				journeys.getLast().setDestination(edge.getDestination());
			}else{
				journeys.add(new Journey(path.get(i-1).getDestination(), edge.getDestination(), edge.getMode(), edge.getColor()));
				changes++;
			}
			i++;
		}
//		System.out.println(journeys);
		return new Route<Journey>(journeys, stopCount, changes);
	}

	public List<Route<Journey>> getRoute(Vertex sourceVertex, Vertex destinationVertex) throws RouteNotFoundException {
		Tree<DirectedEdge> tree = new Tree<DirectedEdge>();
		Node<DirectedEdge> sourceNode = new Node<DirectedEdge>(new DirectedEdge(null, sourceVertex, null, null));
		tree.setRootElement(sourceNode);
		
		Journey suffixJourney = new Journey(null, destinationVertex, Mode.WALK, Color.BLUE); 
		while(!this.graph.containsKey(destinationVertex)){
			destinationVertex = getNearestVertex(destinationVertex);
		}
		suffixJourney.setSource(destinationVertex);
		
		getRoute(sourceNode, new HashSet<DirectedEdge>(), destinationVertex, tree);
		List<Node<DirectedEdge>> result = find(tree, destinationVertex);
		if(result.isEmpty()){
			throw new RouteNotFoundException();
		}
		List<Journey> suffix = new ArrayList<Journey>();
		if(!suffixJourney.getSource().getCode().equals(suffixJourney.getDestination().getCode())){
			suffix.add(suffixJourney);
		}
		return findPaths(result, suffix);
	}
	private Vertex getNearestVertex(Vertex destinationVertex) {
		StopNode node =LocationUtil.getClosestNode(destinationVertex);
		return new Vertex(node);
	}

	private List<Route<Journey>> findPaths(List<Node<DirectedEdge>> result, List<Journey> suffixJourney) {
		List<Route<Journey>> routes = new ArrayList<Route<Journey>>();
		for (Node<DirectedEdge> node : result) {
			Route<Journey> route = getRoute(node);
			route.suffix(suffixJourney);
			routes.add(route);
		}
		Collections.sort(routes, new Comparator<Route<Journey>>() {

			@Override
			public int compare(Route<Journey> first, Route<Journey> second) {
				return first.getChanges() - second.getChanges();
			}
		});
		return routes;
	}
	private Route<Journey> getRoute(Node<DirectedEdge> node) {
		List<DirectedEdge> path = new ArrayList<DirectedEdge>();
		int stopCount = 0;
		while (node.getParent() !=null) {
			path.add(node.getData());
			node = node.getParent();
			stopCount++;
		}
		path.add(node.getData());
		//Collections.reverse(path);
		return filter(path, stopCount);
	}
	
	private List<Node<DirectedEdge>> find(Tree<DirectedEdge> tree, Vertex nodeData){
		List<Node<DirectedEdge>> nodes = new ArrayList<Node<DirectedEdge>>();
		findNode(nodeData, tree.getRootElement().getChildren(), nodes);
		return nodes;
	}

	private void findNode(Vertex nodeData, List<Node<DirectedEdge>> scanList, List<Node<DirectedEdge>> resultList){
		List<Node<DirectedEdge>> nodesData = new ArrayList<Node<DirectedEdge>>();
		for(Node<DirectedEdge> node : scanList){
			if(node.getData().getDestination().getCode().equals(nodeData.getCode()))
				resultList.add(node);
			nodesData.addAll(node.getChildren());	
		}
		if(!nodesData.isEmpty()){
			findNode(nodeData, nodesData, resultList);
		}
	}
	private void getRoute(Node<DirectedEdge> parentEdgeNode,
			Set<DirectedEdge> visitedEdges, Vertex destinationVertex, Tree<DirectedEdge> tree) {
		System.out.println(parentEdgeNode);
		Set<DirectedEdge> linksSet = graph.get(parentEdgeNode.getData().getDestination());
		List<DirectedEdge> links = new ArrayList<DirectedEdge>(linksSet);
		Collections.sort(links, new EdgeColorComparator());
		if(links!=null){
			for (DirectedEdge link : links) {
				Node<DirectedEdge> childEdgeNode = new Node<DirectedEdge>(link);
				parentEdgeNode.addChild(childEdgeNode);
				DirectedEdge newEdge = new DirectedEdge(childEdgeNode.getData().getDestination(), parentEdgeNode.getData().getDestination(), childEdgeNode.getData().getMode(), childEdgeNode.getData().getColor());
				System.out.println(link.equals(newEdge));
				visitedEdges.add(newEdge);
				if(!childEdgeNode.getData().getDestination().getCode().equals(destinationVertex.getCode()) && !visitedEdges.contains(link)){
					//System.out.println(childEdgeNode);
					getRoute(childEdgeNode, visitedEdges, destinationVertex, tree);
				}
			}
		}
	}

}
